<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    ul, li {
      list-style: none;
      margin: 0;
      padding: 0;
    }

    ul {
      padding-left: 24px;
      border-left: 1px dashed #dddddd;
    }

    .box {
      font-size: 14px;
      line-height: 1.5;
      position: relative;
    }

    .box > ul {
      display: none;
    }

    .box.box__open > ul {
      display: block;
    }

    .ellipsis__hide {
      display: none;
    }

    .box-pointer {
      position: absolute;
      cursor: pointer;
      display: inline-block;
      text-align: center;
      width: 14px;
      left: -14px;
    }

    .box-bracket {
      display: inline-block;
    }
  </style>
</head>
<body style="padding-left: 24px">
<script>
  /**
   * 创建dom元素
   *
   * @param type 创建的dom标签类型
   * @param props 所创建dom上的属性
   * @param children 所创建dom的子元素
   * */
  function _createElement (type, props, ...children) {
    const dom = document.createElement(type || 'div')
    if (props) {
      for (const key in props) {
        dom.setAttribute(key, props[key])
      }
    }
    if (children) {
      for (const child of children) {
        if (child === null || child === void 0) {
          dom.appendChild(document.createTextNode(''))
        } else if (
          typeof child === 'string' ||
          typeof child === 'number' ||
          typeof child === 'boolean'
        ) {
          dom.appendChild(document.createTextNode(child))
        } else {
          dom.appendChild(child)
        }
      }
    }

    return dom
  }

  /**
   * 可视化函数
   *
   * @param data 可视化的数据
   * @param fieldName 父节点字段名
   * @param isLast 是否是最后一个属性
   * */
  function jsonParse (data, fieldName, isLast) {
    // 不是数组或对象 就返回一个li直接展示
    if (typeof data !== 'object') {
      return _createElement('li', null,
        fieldName ? _createElement('b', null, fieldName) : '',
        fieldName ? ': ' : '',
        data,
        isLast ? '' : ','
      )
    }

    // 是否是数组
    const _is_array = Array.isArray(data)

    // 创建容器
    const box = _createElement('div', { class: 'box box__open' })

    // 创建并追加 +或- 元素
    const boxPointer = _createElement('span', { class: 'box-pointer' }, '+')
    // 增加点击事件
    boxPointer.onclick = () => {
      if (boxPointer.textContent === '+') {
        box.className = 'box'
        ellipsis.className = ''
        boxPointer.textContent = '-'
      } else {
        box.className = 'box box__open'
        ellipsis.className = 'ellipsis__hide'
        boxPointer.textContent = '+'
      }
    }
    box.appendChild(boxPointer)

    // 创建并追加父字段名
    if (fieldName) {
      const fieldDom = _createElement('span', null,
        _createElement('b', null, fieldName),
        ': '
      )
      box.appendChild(fieldDom)
    }

    // 创建并追加左括号
    const boxBracketLeft = _createElement('span', null,
      _is_array ? '[' : '{'
    )
    box.appendChild(boxBracketLeft)

    // 创建并追加... 展开隐藏，收起显示
    const ellipsis = _createElement('span', { class: 'ellipsis__hide' }, '...')
    box.appendChild(ellipsis)

    // 创建ul容器
    const ul = document.createElement('ul')
    box.appendChild(ul)

    if (_is_array) {
      // 是数组
      for (let i = 0, len = data.length; i < len; i ++) {
        ul.appendChild(jsonParse(data[i], '', i === len - 1))
      }
    } else {
      // 是对象
      const len = Object.keys(data).length
      let count = 0
      for (const key in data) {
        count ++
        ul.appendChild(jsonParse(data[key], key, count === len))
      }
    }

    // 创建并追加右括号
    const boxBracketRight = _createElement('span', null,
      _is_array ? ']' : '}',
      isLast ? '' : ','
    )
    box.appendChild(boxBracketRight)

    return box
  }

  const jsonObj = {
    da_ge: '刘备',
    er_ge: '关羽',
    san_di: '张飞',
    shu_guo: {
      zhu_gong: '刘皇叔',
      jun_shi: '诸葛亮'
    },
    cao_cao: {
      zi: '孟德',
      age: 66,
      sons: ['曹昂', 34, true, {
        name: '曹丕',
        age: 40
      }]
    }
  }
  document.body.appendChild(jsonParse(jsonObj))

</script>
</body>
</html>